package service

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"prome-shard/common"
	"prome-shard/config"
	"prome-shard/consistent"
	"prome-shard/goansiblerun"
	"prome-shard/target"
	"sort"
	"strings"
	"sync"
	"time"
)

type ShardService struct {
	FileType       string                 // yaml or json
	SrvName        string                 //服务名称
	ring           *consistent.Consistent //一致性哈希环
	DestSdFileName string                 // json文件在目标机器上的名字
	YamlPath       string                 // 执行的ansible playbook yaml
	Nodes          []string               //节点
	Port           int                    //端口
	TargetGetFunc  target.GetTargetFunc   // 对应获取target的方法
	logger         log.Logger
	NodeUpdateChan chan []string // 节点变更的通知chan
	ctx            context.Context
	sync.RWMutex
}

// 根据传递进来的节点 创建一致性hash环
func (this *ShardService) ReShardRing(nodes []string) {
	this.Lock()
	defer this.Unlock()
	newRing := consistent.NewConsistent(common.Replicas)
	for _, node := range nodes {
		newRing.Add(node)
	}
	this.ring = newRing
}

// 设置节点的方法,将节点放入一致性hash环
func (this *ShardService) SetNodes(nodes []string) {
	for _, node := range nodes {
		this.ring.Add(node)
	}
}

// 根据pk,获取node节点. chash(pk) -> node
func (this *ShardService) GetNode(pk string) string {
	this.RLock()
	defer this.RUnlock()

	return this.ring.Get(pk)
}
// 初始化ShardService
func NewShardService(cg *config.ShardService, ctx context.Context, logger log.Logger) *ShardService {
	tf := target.AvaiableGetTargetFuncs[cg.Name]

	r := consistent.NewConsistent(common.Replicas)

	s := &ShardService{
		SrvName:        cg.Name,
		FileType:       cg.FileType,
		DestSdFileName: cg.DestSdFileName,
		YamlPath:       cg.YamlPath,
		ring:           r,
		Nodes:          cg.Nodes,
		Port:           cg.Port,
		ctx:            ctx,
		logger:         logger,
		TargetGetFunc:  tf,
		NodeUpdateChan: make(chan []string, 1),
	}
	// 将每一个服务对应的节点放入一致性hash环
	s.SetNodes(cg.Nodes)
	// 开启一致性哈希环变更监听
	go s.RunReshardHashRing()
	return s
}
// 两个string切片的元素比较是否相等的函数
func StringSliceEqualBCE(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	if (a == nil) != (b == nil) {
		return false
	}

	b = b[:len(a)]
	for i, v := range a {
		if v != b[i] {
			return false
		}
	}
	return true
}

// 开启一致性hash环的变更监听
func (this *ShardService) RunReshardHashRing() {

	level.Info(this.logger).Log("msg", "RunRefreshServiceNode start....")
	// 死循环一直监听
	for {
		select {
		case nodes := <-this.NodeUpdateChan:

			oldNodes := this.ring.Members()
			sort.Strings(nodes)
			sort.Strings(oldNodes)
			isEq := StringSliceEqualBCE(nodes, oldNodes)
			if isEq == false {
				level.Info(this.logger).Log("msg", "RunReshardHashRing_node_update_reshard", "old_num", len(oldNodes), "new_num", len(nodes), "oldnodes", strings.Join(oldNodes, ","), "newnodes", strings.Join(nodes, ","))
				this.ReShardRing(nodes) // 根据最新的重写分配
				// 立即进行再一次再分配
				this.Dispatch()
			} else {
				level.Info(this.logger).Log("msg", "RunReshardHashRing_node_same", "nodes", strings.Join(nodes, ","))
			}
		// 通过标志位将监听退出
		case <-this.ctx.Done():
			level.Info(this.logger).Log("msg", "RunReshardHashRingQuit")
			return
		}

	}
}

func (this *ShardService) Dispatch() {
	// 执行这个对应的获取target函数
	// node_exporter获取node_exporter的target
	// alert_rule获取alert_rule的target
	targets := this.TargetGetFunc()
	if len(targets) == 0 {
		level.Warn(this.logger).Log("msg", "Dispatch.empty.targets")
		return
	}
	// 先初始化一个map ，key是 节点，value是分配给这个节点的targets
	nodeMap := make(map[string][]target.ScrapeTarget)

	// 遍历target，
	for _, t := range targets {

		switch t.Type {
		case common.TargetScrape:
			t := t
			if len(t.Targets) != 1 {
				continue
			}
			// 对target的地址 在哈希环中寻找节点
			// 要求每个target的地址都是1个
			// 然后根据node塞入map中
			// 也就是找到每一个target对应哪一个prometheus
			node := this.GetNode(t.Targets[0])

			preTs, loaded := nodeMap[node]
			if !loaded {
				preTs = make([]target.ScrapeTarget, 0)

			}
			preTs = append(preTs, t)
			nodeMap[node] = preTs
		case common.TargetAlert:
			t := t

			// 对target的地址 在哈希环中寻找节点
			// 要求每个target的地址都是1个
			// 然后根据node塞入map中
			node := this.GetNode(t.AlertName)

			preTs, loaded := nodeMap[node]
			if !loaded {
				preTs = make([]target.ScrapeTarget, 0)

			}
			preTs = append(preTs, t)
			nodeMap[node] = preTs
		}

	}
	index := 1
	allNum := len(nodeMap)
	for node, ts := range nodeMap {
		// 拼接一个json文件的名字
		// 服务名_节点ip_索引_分片总数_target总数.json
		dstFileName := ""
		// 写json文件
		switch this.FileType {
		case "json":
			dstFileName = fmt.Sprintf("%s_%s_%d_%d_%d.json",
				this.SrvName,
				node,
				index,
				allNum,
				len(ts),
			)

			writeJsonFile(dstFileName, ts)
		case "yaml":
			dstFileName = fmt.Sprintf("%s_%s_%d_%d_%d.yaml",
				this.SrvName,
				node,
				index,
				allNum,
				len(ts),
			)
			writeYamlFile(dstFileName, ts)

		}

		extraVars := make(map[string]interface{})
		extraVars["src_sd_file_name"] = dstFileName
		extraVars["dest_sd_file_name"] = this.DestSdFileName
		extraVars["service_port"] = this.Port
		level.Info(this.logger).Log(
			"msg", "goansiblerun.run",

			"this.SrvName", this.SrvName,
			"jsonFileName", dstFileName,
			"node", node,
			"index", index,
			"all", allNum,
			"targetNum", len(ts),
		)
		go goansiblerun.AnsiRunPlay(this.logger, this.SrvName, node, extraVars, this.YamlPath)
		index++
	}

}

func (this *ShardService) RunDispatch() error {
	level.Info(this.logger).Log("msg", "RunDispatch.start", "name", this.SrvName)
	ticker := time.NewTicker(1 * time.Minute)
	this.Dispatch()
	defer ticker.Stop()
	for {
		select {
		case <-this.ctx.Done():
			level.Info(this.logger).Log("msg", "receive_quit_signal_and_quit")
			return nil
		case <-ticker.C:
			//level.Info(logger).Log("msg", "doIndexSync_run")
			this.Dispatch()
		}

	}
}

func writeJsonFile(fileName string, ts []target.ScrapeTarget) {

	bs, _ := json.Marshal(ts)

	err := ioutil.WriteFile(fileName, bs, 0644)
	fmt.Println(err)
}

type Groups struct {
	Gs []RuleGroup `yaml:"groups"`
}

type RuleGroup struct {
	Name  string `yaml:"name"`
	Rules []Rule `yaml:"rules"`
}

// Rule describes an alerting or recording rule.
type Rule struct {
	Record      string            `yaml:"record,omitempty"`
	Alert       string            `yaml:"alert,omitempty"`
	Expr        string            `yaml:"expr"`
	For         string            `yaml:"for,omitempty"`
	Labels      map[string]string `yaml:"labels,omitempty"`
	Annotations map[string]string `yaml:"annotations,omitempty"`
}

func writeYamlFile(fileName string, ts []target.ScrapeTarget) {
	gs := make([]RuleGroup, 0)

	for _, t := range ts {
		rules := make([]Rule, 0)
		r := Rule{
			Alert:       t.AlertName,
			Expr:        t.Expr,
			For:         t.For,
			Labels:      t.AlertLabels,
			Annotations: t.AlertAnnotations,
		}

		rules = append(rules, r)
		g := RuleGroup{
			Name:  t.AlertName,
			Rules: rules,
		}
		gs = append(gs, g)
	}
	g := Groups{Gs: gs}
	bs, _ := yaml.Marshal(g)

	err := ioutil.WriteFile(fileName, bs, 0644)
	fmt.Println(err)
}
